Coverage Report

Created: 2024-12-19 06:34

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
D:\a\tools.proto\tools.proto\runtime\src\codec\bits.rs
Line
Count
Source
1
// Copyright (c) 2024, BlockProject 3D
2
//
3
// All rights reserved.
4
//
5
// Redistribution and use in source and binary forms, with or without modification,
6
// are permitted provided that the following conditions are met:
7
//
8
//     * Redistributions of source code must retain the above copyright notice,
9
//       this list of conditions and the following disclaimer.
10
//     * Redistributions in binary form must reproduce the above copyright notice,
11
//       this list of conditions and the following disclaimer in the documentation
12
//       and/or other materials provided with the distribution.
13
//     * Neither the name of BlockProject 3D nor the names of its contributors
14
//       may be used to endorse or promote products derived from this software
15
//       without specific prior written permission.
16
//
17
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
21
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29
use crate::util::ToUsize;
30
use bytesutil::{ReadBytes, WriteBytes};
31
use std::ops::{BitAnd, BitOr, Shl, Shr};
32
33
pub trait BitCodec {
34
    /// Reads a value of type T from the buffer argument with a custom bit offset and size assuming
35
    /// the buffer size is greater or equal to the size of T.
36
    ///
37
    /// # Arguments
38
    ///
39
    /// * `buffer`: the buffer to read from.
40
    ///
41
    /// returns: T
42
    ///
43
    /// # Safety
44
    ///
45
    /// This function assumes that the length of the buffer passed in as argument is at least as
46
    /// large as the size of T. Currently, this relies on bytesutil which does not apply any
47
    /// optimization and as such passing a too small buffer will only panic, however a future
48
    /// optimization might remove the panic check from release builds, essentially causing UB in
49
    /// such build.
50
    unsafe fn read_aligned<
51
        T: ToUsize + ReadBytes + Shr<Output = T> + BitAnd<Output = T>,
52
        const BIT_OFFSET: usize,
53
        const BIT_SIZE: usize,
54
    >(
55
        buffer: &[u8],
56
    ) -> T;
57
58
    /// Reads a value of type T from the buffer argument with a custom bit offset and size assuming
59
    /// the buffer size is always less than the size of T. This is not unsafe as will always cause
60
    /// a copy into an 8 bytes buffer (the maximum size for T is 8).
61
    ///
62
    /// # Arguments
63
    ///
64
    /// * `buffer`: the buffer to read from.
65
    ///
66
    /// returns: T
67
    fn read_unaligned<
68
        T: ToUsize + ReadBytes + Shr<Output = T> + BitAnd<Output = T>,
69
        const BIT_OFFSET: usize,
70
        const BIT_SIZE: usize,
71
    >(
72
        buffer: &[u8],
73
    ) -> T;
74
75
8
    fn read<
76
8
        T: ToUsize + ReadBytes + Shr<Output = T> + BitAnd<Output = T>,
77
8
        const BIT_OFFSET: usize,
78
8
        const BIT_SIZE: usize,
79
8
    >(
80
8
        buffer: &[u8],
81
8
    ) -> T {
82
8
        if size_of::<T>() != buffer.len() {
  Branch (82:12): [True: 0, False: 1]
  Branch (82:12): [True: 0, False: 1]
  Branch (82:12): [True: 0, False: 2]
  Branch (82:12): [True: 0, False: 1]
  Branch (82:12): [True: 0, False: 1]
  Branch (82:12): [True: 0, False: 1]
  Branch (82:12): [True: 0, False: 1]
  Branch (82:12): [Folded - Ignored]
83
0
            Self::read_unaligned::<T, BIT_OFFSET, BIT_SIZE>(buffer)
84
        } else {
85
8
            unsafe { Self::read_aligned::<T, BIT_OFFSET, BIT_SIZE>(buffer) }
86
        }
87
8
    }
88
89
    /// Writes a value of type T in the buffer argument with a custom bit offset and size assuming
90
    /// the buffer size is greater or equal to the size of T.
91
    ///
92
    /// # Arguments
93
    ///
94
    /// * `buffer`: the buffer to write to.
95
    /// * `value`: the value to write.
96
    ///
97
    /// # Safety
98
    ///
99
    /// This function assumes that the length of the buffer passed in as argument is at least as
100
    /// large as the size of T. Currently, this relies on bytesutil which does not apply any
101
    /// optimization and as such passing a too small buffer will only panic, however a future
102
    /// optimization might remove the panic check from release builds, essentially causing UB in
103
    /// such build.
104
    unsafe fn write_aligned<
105
        T: ToUsize + ReadBytes + WriteBytes + Shl<Output = T> + Shr<Output = T> + BitAnd<Output = T> + BitOr<Output = T>,
106
        const BIT_OFFSET: usize,
107
        const BIT_SIZE: usize,
108
    >(
109
        buffer: &mut [u8],
110
        value: T,
111
    );
112
113
    fn write_unaligned<
114
        T: ToUsize + ReadBytes + WriteBytes + Shl<Output = T> + Shr<Output = T> + BitAnd<Output = T> + BitOr<Output = T>,
115
        const BIT_OFFSET: usize,
116
        const BIT_SIZE: usize,
117
    >(
118
        buffer: &mut [u8],
119
        value: T,
120
    );
121
122
3
    fn write<
123
3
        T: ToUsize + ReadBytes + WriteBytes + Shl<Output = T> + Shr<Output = T> + BitAnd<Output = T> + BitOr<Output = T>,
124
3
        const BIT_OFFSET: usize,
125
3
        const BIT_SIZE: usize,
126
3
    >(
127
3
        buffer: &mut [u8],
128
3
        value: T,
129
3
    ) {
130
3
        if size_of::<T>() != buffer.len() {
  Branch (130:12): [True: 0, False: 1]
  Branch (130:12): [True: 0, False: 1]
  Branch (130:12): [True: 0, False: 1]
  Branch (130:12): [Folded - Ignored]
131
0
            Self::write_unaligned::<T, BIT_OFFSET, BIT_SIZE>(buffer, value);
132
3
        } else {
133
3
            unsafe { Self::write_aligned::<T, BIT_OFFSET, BIT_SIZE>(buffer, value) };
134
3
        }
135
3
    }
136
}
137
138
pub struct BitCodecLE;
139
pub struct BitCodecBE;
140
141
impl BitCodec for BitCodecLE {
142
60
    unsafe fn read_aligned<
143
60
        T: ToUsize + ReadBytes + Shr<Output = T> + BitAnd<Output = T>,
144
60
        const BIT_OFFSET: usize,
145
60
        const BIT_SIZE: usize,
146
60
    >(
147
60
        buffer: &[u8],
148
60
    ) -> T {
149
60
        let mask: usize = (1 << BIT_SIZE) - 1;
150
60
        let value = T::read_bytes_le(buffer);
151
60
        (value >> T::from_usize(BIT_OFFSET)) & T::from_usize(mask)
152
60
    }
153
154
4
    fn read_unaligned<
155
4
        T: ToUsize + ReadBytes + Shr<Output = T> + BitAnd<Output = T>,
156
4
        const BIT_OFFSET: usize,
157
4
        const BIT_SIZE: usize,
158
4
    >(
159
4
        buffer: &[u8],
160
4
    ) -> T {
161
4
        let mut data = [0; 8];
162
4
        data[..buffer.len()].copy_from_slice(buffer);
163
4
        unsafe { Self::read_aligned::<T, BIT_OFFSET, BIT_SIZE>(&data) }
164
4
    }
165
166
53
    unsafe fn write_aligned<
167
53
        T: ToUsize + ReadBytes + WriteBytes + Shl<Output = T> + Shr<Output = T> + BitAnd<Output = T> + BitOr<Output = T>,
168
53
        const BIT_OFFSET: usize,
169
53
        const BIT_SIZE: usize,
170
53
    >(
171
53
        buffer: &mut [u8],
172
53
        value: T,
173
53
    ) {
174
53
        let mask: usize = (1 << BIT_SIZE) - 1;
175
53
        let reset_mask = !(mask << BIT_OFFSET);
176
53
        let original = T::read_bytes_le(buffer);
177
53
        let clean = original & T::from_usize(reset_mask);
178
53
        let value = (value & T::from_usize(mask)) << T::from_usize(BIT_OFFSET);
179
53
        (clean | value).write_bytes_le(buffer);
180
53
    }
181
182
5
    fn write_unaligned<
183
5
        T: ToUsize + ReadBytes + WriteBytes + Shl<Output = T> + Shr<Output = T> + BitAnd<Output = T> + BitOr<Output = T>,
184
5
        const BIT_OFFSET: usize,
185
5
        const BIT_SIZE: usize,
186
5
    >(
187
5
        buffer: &mut [u8],
188
5
        value: T,
189
5
    ) {
190
5
        let mut data = [0; 8];
191
5
        data[..buffer.len()].copy_from_slice(buffer);
192
5
        unsafe { Self::write_aligned::<T, BIT_OFFSET, BIT_SIZE>(&mut data, value) };
193
5
        let motherfuckingrust = buffer.len();
194
5
        buffer.copy_from_slice(&data[..motherfuckingrust]);
195
5
    }
196
}
197
198
impl BitCodec for BitCodecBE {
199
39
    unsafe fn read_aligned<
200
39
        T: ToUsize + ReadBytes + Shr<Output = T> + BitAnd<Output = T>,
201
39
        const BIT_OFFSET: usize,
202
39
        const BIT_SIZE: usize,
203
39
    >(
204
39
        buffer: &[u8],
205
39
    ) -> T {
206
39
        let mask: usize = (1 << BIT_SIZE) - 1;
207
39
        let value = T::read_bytes_be(buffer);
208
39
        (value >> T::from_usize(8 - (BIT_SIZE % 8) - BIT_OFFSET)) & T::from_usize(mask)
209
39
    }
210
211
3
    fn read_unaligned<
212
3
        T: ToUsize + ReadBytes + Shr<Output = T> + BitAnd<Output = T>,
213
3
        const BIT_OFFSET: usize,
214
3
        const BIT_SIZE: usize,
215
3
    >(
216
3
        buffer: &[u8],
217
3
    ) -> T {
218
3
        let offset = size_of::<T>() - buffer.len();
219
3
        let mut data = [0; 8];
220
3
        data[offset..buffer.len() + offset].copy_from_slice(buffer);
221
3
        unsafe { Self::read_aligned::<T, BIT_OFFSET, BIT_SIZE>(&data) }
222
3
    }
223
224
33
    unsafe fn write_aligned<
225
33
        T: ToUsize + ReadBytes + WriteBytes + Shl<Output = T> + Shr<Output = T> + BitAnd<Output = T> + BitOr<Output = T>,
226
33
        const BIT_OFFSET: usize,
227
33
        const BIT_SIZE: usize,
228
33
    >(
229
33
        buffer: &mut [u8],
230
33
        value: T,
231
33
    ) {
232
33
        let mask: usize = (1 << BIT_SIZE) - 1;
233
33
        let reset_mask = !(mask << (8 - (BIT_SIZE % 8) - BIT_OFFSET));
234
33
        let original = T::read_bytes_be(buffer);
235
33
        let clean = original & T::from_usize(reset_mask);
236
33
        let value = (value & T::from_usize(mask)) << T::from_usize(8 - (BIT_SIZE % 8) - BIT_OFFSET);
237
33
        (clean | value).write_bytes_be(buffer);
238
33
    }
239
240
4
    fn write_unaligned<
241
4
        T: ToUsize + ReadBytes + WriteBytes + Shl<Output = T> + Shr<Output = T> + BitAnd<Output = T> + BitOr<Output = T>,
242
4
        const BIT_OFFSET: usize,
243
4
        const BIT_SIZE: usize,
244
4
    >(
245
4
        buffer: &mut [u8],
246
4
        value: T,
247
4
    ) {
248
4
        let offset = size_of::<T>() - buffer.len();
249
4
        let mut data = [0; 8];
250
4
        data[offset..buffer.len() + offset].copy_from_slice(buffer);
251
4
        unsafe { Self::write_aligned::<T, BIT_OFFSET, BIT_SIZE>(&mut data, value) };
252
4
        let motherfuckingrust = buffer.len();
253
4
        buffer.copy_from_slice(&data[offset..motherfuckingrust + offset]);
254
4
    }
255
}
256
257
#[cfg(test)]
258
mod tests {
259
    use crate::codec::{BitCodec, BitCodecBE, BitCodecLE};
260
261
    #[test]
262
1
    fn little_endian() {
263
1
        let buffer = [0xFF, 0xFF, 0xFF, 0xFF];
264
1
        assert_eq!(BitCodecLE::read::<u32, 0, 32>(&buffer[0..4]), 0xFFFFFFFF);
265
1
        assert_eq!(BitCodecLE::read::<u8, 0, 1>(&buffer[0..1]), 1);
266
1
        assert_eq!(BitCodecLE::read::<u8, 0, 4>(&buffer[0..1]), 0xF);
267
1
        assert_eq!(BitCodecLE::read::<u8, 4, 4>(&buffer[0..1]), 0xF);
268
1
    }
269
270
    #[test]
271
1
    fn big_endian() {
272
1
        let buffer = [0xAB, 0xF0];
273
1
        assert_eq!(BitCodecBE::read::<u16, 0, 12>(&buffer[0..2]), 0xABF);
274
1
        let mut buffer = [0x0, 0x0];
275
1
        BitCodecBE::write::<u8, 0, 4>(&mut buffer[0..1], 0xF);
276
1
        assert_eq!(BitCodecBE::read::<u8, 0, 4>(&buffer[0..1]), 0xF);
277
1
        BitCodecBE::write::<u16, 0, 12>(&mut buffer[0..2], 0xABF);
278
1
        assert_eq!(BitCodecBE::read::<u16, 0, 12>(&buffer[0..2]), 0xABF);
279
1
        BitCodecBE::write::<u8, 1, 7>(&mut buffer[1..2], 127);
280
1
        assert_eq!(BitCodecBE::read::<u8, 1, 7>(&buffer[1..2]), 127);
281
1
    }
282
}